/**
 * @ignore
 * BEGIN HEADER
 *
 * Contains:        ZettlrProject class
 * CVM-Role:        Model
 * Maintainer:      Hendrik Erz
 * License:         GNU GPL v3
 *
 * Description:     This file contains a class that provides functions for a
 *                  functional project deployment in Zettlr. It extends the
 *                  functionality of ZettlrDir to also be able to export all
 *                  files in one single PDF file. Therefore here's the
 *                  functionality that you need to write whole books!
 *
 * END HEADER
 */

const fs = require('fs')
const path = require('path')
const makeExport = require('./zettlr-export.js')
const sanitize = require('sanitize-filename')
const { flattenDirectoryTree } = require('../common/zettlr-helpers.js')

const PROJECT_FILE = '.ztr-project'

/**
 * ZettlrProjects adds project functionality to any given directory. It manages
 * its own settings via a small file inside the directory and handles exports.
 */
class ZettlrProject {
  /**
    * Creates the instance and reads the project file, if there is one.
    */
  constructor (directory) {
    this._dir = directory
    this._cfgtpl = {
      // This object contains all necessary config values. This classes'
      // functionality has been taken from ZettlrConfig a lot.
      'pdf': {
        'author': 'Generated by Zettlr', // Default user name
        'keywords': '', // PDF keywords
        'papertype': 'a4paper', // Paper to use, e.g. A4 or Letter
        'pagenumbering': 'arabic',
        'tmargin': 3, // Margins to paper (top, right, bottom, left)
        'rmargin': 3,
        'bmargin': 3,
        'lmargin': 3,
        'margin_unit': 'cm',
        'lineheight': '1.2',
        'mainfont': 'Times New Roman',
        'sansfont': 'Arial',
        'fontsize': 12,
        'toc': true, // Default: generate table of contents
        'tocDepth': 2, // Default: evaluate until level 2
        'titlepage': true, // Generate a title page by default
        'textpl': '' // Can be used to store a custom TeX template
      },
      'title': this._dir.name, // Default project title is the directory's name
      'format': 'pdf', // Default export format: pdf.
      'cslStyle': '' // A CSL style file, if applicable.
    }
    this._cfg = null
    this._projectFile = path.join(this._dir.path, PROJECT_FILE)

    this._read()
  }

  /**
    * Reads the project file from disk or creates a file, if there is none.
    */
  _read () {
    this._cfg = this._cfgtpl
    try {
      fs.lstatSync(this._projectFile)
      this.update(JSON.parse(fs.readFileSync(this._projectFile, 'utf8')))
    } catch (e) {
      this.save() // Simply create the file
    }
  }

  /**
    * Saves changes to the project to disk.
    */
  save () {
    fs.writeFileSync(this._projectFile, JSON.stringify(this._cfg), 'utf8')
  }

  /**
    * This function builds the complete project at once.
    */
  build () {
    // Receive a two dimensional array of all directory contents
    let files = flattenDirectoryTree(this._dir)

    // Reduce to files-only
    for (let i = 0; i < files.length; i++) {
      if (files[i].type !== 'file') {
        files.splice(i, 1)
        i--
      }
    }

    // Concat the files
    let contents = []
    for (let file of files) {
      // Directly make all image paths absolute to prevent errors if used
      // in nested project directories (in which this._dir.path is not the
      // same for all files). Also make the footnotes unique to prevent
      // assigning errors.
      contents.push(file.read({
        'absoluteImagePaths': true,
        'uniqueFootnotes': true
      }))
    }

    // Make one string
    contents = contents.join('\n\n')

    // Mock a file object to which ZettlrExport has access
    let tempfile = {
      'path': path.join(this._dir.path, sanitize(this._cfg.title, { replacement: '-' })),
      'name': sanitize(this._cfg.title, { replacement: '-' }), // obvious filename
      'read': (opt) => { return contents }
    }

    // Start up the Exporter
    let opt = {
      'format': this._cfg.format, // Which format: "html", "docx", "odt", "pdf"
      'file': tempfile, // The file to be exported
      'dest': this._dir.path, // On project exports, always dir path
      'stripIDs': true,
      'stripTags': true,
      'stripLinks': 'full',
      'pdf': this._cfg.pdf,
      'title': this._cfg.title,
      'author': this._cfg.pdf.author,
      'keywords': this._cfg.pdf.keywords,
      'cslStyle': this._cfg.cslStyle
    }

    // Aaaand export.
    makeExport(opt)
      .then((exporter) => { /* Nothing to do */ })
      .catch((err) => { global.ipc.notifyError(err) })
  }

  /**
    * Removes the project file
    * @return {null} Always returns null.
    */
  remove () {
    // This removes the project file.
    try {
      fs.lstatSync(this._projectFile)
      fs.unlink(this._projectFile, (err) => {
        if (err) {
          // Do nothing. Worst thing that could happen is that the user
          // some day discovers an abandoned file on his/her disk.
        }
      })
    } catch (e) {
      // No file present, so let's simply do nothing.
    }

    return null
  }

  /**
    * Returns the Project properties
    * @return {Object} The properties for this project.
    */
  getProperties () {
    return this._cfg
  }

  /**
    * Update the complete configuration object with new values
    * @param  {Object} newcfg               The new object containing new props
    * @param  {Object} [oldcfg=this.config] Necessary for recursion
    * @return {void}                      Does not return anything.
    */
  update (newcfg, oldcfg = this._cfg) {
    // Overwrite all given attributes (and leave the not given in place)
    // This will ensure sane defaults.
    for (var prop in oldcfg) {
      if (newcfg.hasOwnProperty(prop) && (newcfg[prop] != null)) {
        // We have some variable-length arrays that only contain
        // strings, e.g. we cannot update them using update()
        if ((typeof oldcfg[prop] === 'object') && !Array.isArray(oldcfg[prop])) {
          // Update sub-object
          this.update(newcfg[prop], oldcfg[prop])
        } else {
          oldcfg[prop] = newcfg[prop]
        }
      }
    }

    // Save the new configuration to disk.
    this.save()
  }

  /**
    * Sets a project property
    * @param {String} option The option to be set
    * @param {Mixed} value  The value of the config variable.
    * @return {Boolean} Whether or not the option was successfully set.
    */
  set (option, value) {
    // Don't add non-existent options
    if (this._cfg.hasOwnProperty(option)) {
      this._cfg[option] = value
      return true
    }

    if (option.indexOf('.') > 0) {
      // A nested argument was requested, so iterate until we find it
      let nested = option.split('.')
      let prop = nested.pop() // Last one must be set manually, b/c simple attributes aren't pointers
      let cfg = this._cfg
      for (let arg of nested) {
        if (cfg.hasOwnProperty(arg)) {
          cfg = cfg[arg]
        } else {
          return false // The config option must match exactly
        }
      }

      // Set the nested property
      if (cfg.hasOwnProperty(prop)) {
        cfg[prop] = value
        return true
      }
    }

    return false
  }

  /**
   * Update the project's settings according to the new object.
   * @param  {Objext} cfgObj An object containing the settings in key:value form.
   * @return {Boolean}        True or false, if an error occurred.
   */
  bulkSet (cfgObj) {
    let ret = true
    for (let opt in cfgObj) {
      if (!this.set(opt, cfgObj[opt])) ret = false
    }
    return ret
  }

  /**
    * Static method used by ZettlrDir to determine whether or not it's a project.
    * @param  {ZettlrDir}  directory The directory for which existence of this file should be testet.
    * @return {Boolean}           Returns true, if a corresponding file has been found, or null.
    */
  static isProject (directory) {
    try {
      fs.lstatSync(path.join(directory.path, PROJECT_FILE))
      return true // A project file has been found, so return true
    } catch (e) {
      return false // No project file present -> return false.
    }
  }
}

module.exports = ZettlrProject
